package com.cwits.wifi.ui.lib;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.jieli.lib.stream.util.Dbug;
import com.jieli.lib.stream.util.ICommon;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.concurrent.LinkedBlockingQueue;

public class MjpegView extends SurfaceView implements SurfaceHolder.Callback, ICommon {
    private static final String tag = MjpegView.class.getSimpleName();

    public final static int SIZE_STANDARD = 1;
    public final static int SIZE_BEST_FIT = 2;
    public final static int SIZE_FULLSCREEN = 3;
    public final static int SIZE_LANDSCAPE_STRETCH = 4;

    private boolean surfaceDone = false;
    private static int displayMode;

    private static BitmapFactory.Options bitmapOptions;

    private static byte[] mBitmapData;

    private VideoThread mVideoThread;
    private AudioThread mAudioThread = null;
    //private AudioTrack mAudioTrack = null;
    private boolean mUpdateCanvas = false;

    public MjpegView(Context context) {
        this(context, null);
    }

    public MjpegView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MjpegView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public byte[] getBitmapData()
    {
        return mBitmapData;
    }

    public void setBitmapData(byte[] bitmapdata)
    {
        mBitmapData = bitmapdata;
    }
    private void init() {
        Dbug.i(tag, "init.........................");
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        setFocusable(true);

        displayMode = SIZE_LANDSCAPE_STRETCH;

        bitmapOptions = new BitmapFactory.Options();
//        bitmapOptions.inSampleSize = 2;
//        bitmapOptions.inScaled = false;
//        bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
        //noinspection deprecation
        bitmapOptions.inPurgeable = true;
    }

    public void clearCanvas() {
        synchronized (getHolder()) {
            Canvas canvas = getHolder().lockCanvas();
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            getHolder().unlockCanvasAndPost(canvas);
        }
    }

    public void drawBitmap(byte[] data){
        drawBitmap (data, false);
    }

    public void drawBitmap(byte[] data, boolean isOptimize){
        if (!surfaceDone) {
            Dbug.w(tag, "Surface not done");
            return;
        }

        if (mVideoThread != null && data != null) {
            mVideoThread.addData(data, isOptimize);
        }
    }

    public void writeAudioData(byte[] audioData) {
        if (mAudioThread != null && audioData != null) {
            mAudioThread.addData(audioData);
        }
    }

    public void updateAudioTrackPlayState(boolean isBuffering){
        if (mAudioThread != null) {
            mAudioThread.updateAudioTrackPlayState(isBuffering);
        }
    }

    public static Bitmap byteToBitmap(byte[] imgByte) {
        InputStream input = null;
        Bitmap bitmap = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 8;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        //noinspection deprecation
        options.inPurgeable = true;
        input = new ByteArrayInputStream(imgByte);
        SoftReference softRef = new SoftReference(BitmapFactory.decodeStream(
                input, null, options));
        bitmap = (Bitmap) softRef.get();
        if (imgByte != null) {
            imgByte = null;
        }

        try {
            if (input != null) {
                input.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return bitmap;
    }


    private static class AudioThread extends Thread {
        private boolean isAudioThreadRunning = false;
        private final LinkedBlockingQueue<byte[]> mBufList = new LinkedBlockingQueue<>(3);
        private AudioTrack mAudioTrack = null;

        public AudioThread () {
            /**Prepare audio track for playing audio*/
            if (mAudioTrack == null){
                final int bufferSize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);

                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_MONO,
                        AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);

                if (mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING){
                    mAudioTrack.play();
                }
                //Dbug.i(tag, "getPlayState=" + mAudioTrack.getPlayState() + ", getState=" + mAudioTrack.getState());
            }
        }

        public void updateAudioTrackPlayState(boolean isBuffering){
            if (mAudioTrack != null) {
                if (isBuffering && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                    mAudioTrack.pause();
                } else {
                    if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
                        mAudioTrack.play();
                    }
                }
            }
        }

        public void addData(byte[] audioData) {
            if (mBufList.remainingCapacity() <= 0) {
                mBufList.poll();
            } else {
                try {
                    mBufList.put(audioData);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //Dbug.w(tag, "audioData =" + audioData.length + ", mBufList=" + mBufList.size());
        }

        public void stopRunning (){
            isAudioThreadRunning = false;

            if (mAudioTrack != null && mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED){
                mAudioTrack.stop();
                mAudioTrack.release();
                mAudioTrack = null;
            }
        }

        @Override
        public void run() {
            super.run();
            isAudioThreadRunning = true;
            synchronized (mBufList) {
                while (isAudioThreadRunning) {
                    byte[] buf = new byte[0];
                    try {
                        buf = mBufList.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (mAudioTrack != null) {
                        mAudioTrack.write(buf, 0, buf.length);
                        //Dbug.i(tag, "Remain mBufList size=" + mBufList.size() + ", remainingCapacity=" + mBufList.remainingCapacity());
                    }
                }
            }
        }
    }


    private static class VideoThread extends Thread {
        private final SurfaceHolder mSurfaceHolder;
        private boolean isVideoThreadRunning = false;
        private final LinkedBlockingQueue<byte[]> mBufList = new LinkedBlockingQueue<>();
        private int dispWidth;
        private int dispHeight;
        private volatile boolean isWaiting = false;
        private boolean isOptimize = false;

        public VideoThread (SurfaceHolder surfaceHolder) {
            mSurfaceHolder = surfaceHolder;
        }
        public void addData(byte[] data, boolean isOptimize) {
            this.isOptimize = isOptimize;
            mBufList.add(data);
            if (isWaiting) {
                synchronized (mBufList) {
                    mBufList.notify();
                }
            }
        }

        public void stopRunning (){
            isVideoThreadRunning = false;
            synchronized (mBufList) {
                mBufList.notify();
                mBufList.clear();
            }
        }

        public void setSurfaceSize(int width, int height) {
//            synchronized (mSurfaceHolder)
            {
                dispWidth = width;
                dispHeight = height;
            }
        }

        private Rect resizeRect(int bitmapWidth, int bitmapHeight) {
            int tempX;
            int tempY;
            if (displayMode == MjpegView.SIZE_STANDARD) {
                tempX = (dispWidth / 2) - (bitmapWidth / 2);
                tempY = (dispHeight / 2) - (bitmapHeight / 2);
                return new Rect(tempX, tempY, bitmapWidth + tempX, bitmapHeight + tempY);
            }
            if (displayMode == MjpegView.SIZE_BEST_FIT) {
                float aspectRatio = (float) bitmapWidth / (float) bitmapHeight;
                bitmapWidth = dispWidth;
                bitmapHeight = (int) (dispWidth / aspectRatio);
                if (bitmapHeight > dispHeight) {
                    bitmapHeight = dispHeight;
                    bitmapWidth = (int) (dispHeight * aspectRatio);
                }
                tempX = (dispWidth / 2) - (bitmapWidth / 2);
                tempY = (dispHeight / 2) - (bitmapHeight / 2);
                return new Rect(tempX, tempY, bitmapWidth + tempX, bitmapHeight + tempY);
            }
            if (displayMode == MjpegView.SIZE_FULLSCREEN) {
                return new Rect(0, 0, dispWidth, dispHeight);
            }

            if (displayMode == SIZE_LANDSCAPE_STRETCH) {
                float DEFAULT_VIDEO_WIDTH = 1280;
                float ratio_width = dispWidth/ DEFAULT_VIDEO_WIDTH;
                float DEFAULT_VIDEO_HEIGHT = 720;
                float ratio_height = dispHeight/ DEFAULT_VIDEO_HEIGHT;
                float aspectRatio = DEFAULT_VIDEO_WIDTH / DEFAULT_VIDEO_HEIGHT;

                int myWidth, myHeight;
                if (dispWidth > dispHeight){
                    myWidth = dispWidth;//(int) (dispHeight * aspectRatio);
                    myHeight = dispHeight;
                    tempX = (dispWidth / 2) - (bitmapWidth / 2);
                    tempY = (dispHeight / 2) - (bitmapHeight / 2);
//                Dbug.i(tag, "tempX =" +tempX + ", tempY=" + tempY + ", myWidth + tempX=" + (myWidth + tempX) + ", myHeight + tempY=" +(myHeight + tempY) +
//                    ", bitmapWidth=" + bitmapWidth + ", bitmapHeight=" +bitmapHeight);
                    //return new Rect(tempX, tempY, myWidth + tempX, myHeight + tempY);
                    return new Rect(0, 0, myWidth, myHeight);
                }else{
                    myWidth = dispWidth;
                    myHeight = (int) (dispWidth / aspectRatio);
//                Dbug.w(tag, "myWidth =" +myWidth + ", myHeight=" + myHeight + ", aspectRatio=" + aspectRatio);
                    if (dispWidth > dispHeight) {
                        return new Rect(0, 0, myWidth, myHeight);
                    } else {
                        float ar = (float) bitmapWidth / (float) bitmapHeight;
                        bitmapWidth = dispWidth;
                        bitmapHeight = (int) (dispWidth / ar);
                        if (bitmapHeight > dispHeight) {
                            bitmapHeight = dispHeight;
                            bitmapWidth = (int) (dispHeight * ar);
                        }
                        tempX = (dispWidth / 2) - (bitmapWidth / 2);
                        tempY = (dispHeight / 2) - (bitmapHeight / 2);
                        //return new Rect(tempX, tempY, bitmapWidth + tempX, bitmapHeight + tempY);
                        return new Rect(0, 0, myWidth, myHeight);
                    }
                }
            }
            return null;
        }

        @Override
        public void run() {
            super.run();
            isVideoThreadRunning = true;
            synchronized (mBufList) {
                while (isVideoThreadRunning) {
                    if (mBufList.isEmpty()) {
                        try {
                            isWaiting = true;
                            mBufList.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        isWaiting = false;
                        mBitmapData = mBufList.remove();
                        Bitmap bitmap;
                        Canvas canvas = null;
                        if (isOptimize) {
                            bitmap = BitmapFactory.decodeByteArray(mBitmapData, 0, mBitmapData.length, bitmapOptions);
                            //bitmap = byteToBitmap(data);
                        } else {
                            bitmap = BitmapFactory.decodeByteArray(mBitmapData, 0, mBitmapData.length);
                        }
                        //bitmap = convert(bitmap,bitmap.getWidth(),bitmap.getHeight());
                        if (bitmap != null){
                            try {
                                //synchronized (mSurfaceHolder)
                                {
                                    canvas = mSurfaceHolder.lockCanvas(null);
                                    Rect destRect = resizeRect(bitmap.getWidth(), bitmap.getHeight());
                                    if (destRect != null && canvas != null){
                                        canvas.drawBitmap(bitmap, null, destRect, null);
                                    } else {
                                        Dbug.e(tag, "Resize destination rectangle fail. canvas=" + canvas + ", destRect=" + destRect);
                                    }
                                }
                            }finally {
                                if (canvas != null) {
                                    mSurfaceHolder.unlockCanvasAndPost(canvas);
                                    if (!bitmap.isRecycled()) {
                                        bitmap.recycle();
                                    }
                                }
                            }
                        } else {
                            Dbug.e(tag, "bitmap is null. data size=" + mBitmapData.length);
                        }
                    }
                }
            }
        }


        Bitmap convert(Bitmap bitmap, int width, int height) {
            int w = bitmap.getWidth();

            int h = bitmap.getHeight();

            Bitmap newbitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);// 创建一个新的和SRC长度宽度一样的位图
            Canvas cv = new Canvas(newbitmap);
            Matrix m = new Matrix();
            //m.postScale(1, -1);   //镜像垂直翻转

            m.postScale(-1, 1);   //镜像水平翻转
            //m.postRotate(-90);  //旋转-90度

            Bitmap new2 = Bitmap.createBitmap(bitmap, 0, 0, w, h, m, true);
            cv.drawBitmap(new2, new Rect(0, 0, new2.getWidth(), new2.getHeight()),new Rect(0, 0, width, height), null);

            return newbitmap;

        }

        public void updateCanvas(){
            Bitmap bitmap;
            if(mBitmapData == null)
                return;
            Canvas canvas = null;
            if (isOptimize) {
                bitmap = BitmapFactory.decodeByteArray(mBitmapData, 0, mBitmapData.length, bitmapOptions);
                //bitmap = byteToBitmap(data);
            } else {
                bitmap = BitmapFactory.decodeByteArray(mBitmapData, 0, mBitmapData.length);
            }
            if (bitmap != null){
                try {
                    //synchronized (mSurfaceHolder)
                    {
                        canvas = mSurfaceHolder.lockCanvas(null);
                        Rect destRect = resizeRect(bitmap.getWidth(), bitmap.getHeight());
                        if (destRect != null && canvas != null){
                            canvas.drawBitmap(bitmap, null, destRect, null);
                        } else {
                            Dbug.e(tag, "Resize destination rectangle fail. canvas=" + canvas + ", destRect=" + destRect);
                        }
                    }
                }finally {
                    if (canvas != null) {
                        mSurfaceHolder.unlockCanvasAndPost(canvas);
                        if (!bitmap.isRecycled()) {
                            bitmap.recycle();
                        }
                    }
                }
            } else {
                Dbug.e(tag, "bitmap is null. data size=" + mBitmapData.length);
            }
        }
    }

    public void setUpdateCanvas(boolean update){
        mUpdateCanvas = update;
    }
    public void release() {
        if (mAudioThread != null) {
            mAudioThread.stopRunning();
            mAudioThread = null;
        }

        if (mVideoThread != null) {
            mVideoThread.stopRunning();
            mVideoThread = null;
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        surfaceDone = true;
        //Dbug.e(tag, "surfaceCreated-----------------------surfaceDone=" + surfaceDone  + ", displayMode=" + displayMode);
        if (mVideoThread == null) {
            mVideoThread = new VideoThread(holder);
        }
        if (mVideoThread.getState() == Thread.State.NEW) {
            /**Start video playback task*/
            mVideoThread.start();
        }
        if (mAudioThread == null) {
            mAudioThread = new AudioThread();
        }
        if (mAudioThread.getState() == Thread.State.NEW) {
            mAudioThread.start();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int f, int w, int h) {
        //Dbug.i(tag, "surfaceChanged--------------w=" + w + ", h=" + h + ", width=" + getWidth() + ", height=" + getHeight() + ", surfaceDone=" + surfaceDone);
        if (mVideoThread != null) {
            mVideoThread.setSurfaceSize(w, h);
            if(mUpdateCanvas)
                mVideoThread.updateCanvas();
            mUpdateCanvas = false;
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //Dbug.w(tag, "surfaceDestroyed------------------------surfaceDone="+ surfaceDone);
        surfaceDone = false;
        release();
    }

    public void setDisplayMode(int s) {
        displayMode = s;
    }
}